package edu.northwestern.cbits.purple_robot_manager.probes.builtin;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.widget.Toast;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.logging.SanityCheck;
import edu.northwestern.cbits.purple_robot_manager.logging.SanityManager;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
public class ApplicationLaunchProbe extends Probe
{
private static final boolean DEFAULT_ENABLED = false;
private static final String WAKE_ACTION = "ACTIVITY_LAUNCH_WAKE";
private static final String DEFAULT_FREQUENCY = "100";
private static final String ENABLED = "config_probe_application_launch_enabled";
private static final String FREQUENCY = "config_probe_application_launch_frequency";
private static final String MUTE_ANDROID_FIVE_WARNING = "config_probe_application_launch_mute_android_five_warning";
private static final boolean DEFAULT_ANDROID_FIVE_WARNING = false;
private PendingIntent _pollIntent = null;
private long _lastInterval = 0;
private String _lastPkgName = null;
private String _lastName = null;
private long _lastStart = 0;
private long _lastCheck = -1;
private HashMap<String, String> _appNames = new HashMap<>();
@Override
public String getPreferenceKey() {
return "built_in_application_launch";
}
@Override
public String name(Context context)
{
return "edu.northwestern.cbits.purple_robot_manager.probes.builtin.ApplicationLaunchProbe";
}
@Override
public String title(Context context)
{
return context.getString(R.string.title_application_launch_probe);
}
@Override
public String probeCategory(Context context)
{
return context.getResources().getString(R.string.probe_device_info_category);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(ApplicationLaunchProbe.ENABLED, true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(ApplicationLaunchProbe.ENABLED, false);
e.commit();
}
@Override
public boolean isEnabled(final Context context)
{
final SharedPreferences prefs = Probe.getPreferences(context);
final AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
boolean disable = false;
long interval = 0;
boolean set = false;
boolean isEnabled = false;
if (super.isEnabled(context))
{
if (prefs.getBoolean(ApplicationLaunchProbe.ENABLED, ApplicationLaunchProbe.DEFAULT_ENABLED))
{
isEnabled = true;
interval = Long.parseLong(prefs.getString(ApplicationLaunchProbe.FREQUENCY, "10000"));
if (interval != this._lastInterval)
{
disable = true;
set = true;
this._lastInterval = interval;
}
}
else
disable = true;
}
else
disable = true;
final ApplicationLaunchProbe me = this;
if (this._pollIntent == null)
{
Intent intent = new Intent(ApplicationLaunchProbe.WAKE_ACTION);
this._pollIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
context.registerReceiver(new BroadcastReceiver()
{
@Override
@SuppressWarnings("deprecation")
public void onReceive(final Context context, Intent intent)
{
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final SanityManager sanity = SanityManager.getInstance(context);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
{
RunningTaskInfo foregroundTaskInfo = activityManager.getRunningTasks(1).get(0);
final String pkgName = foregroundTaskInfo.topActivity.getPackageName();
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo = null;
try {
applicationInfo = packageManager.getApplicationInfo(pkgName, 0);
} catch (final NameNotFoundException e) {
}
final String name = (String) ((applicationInfo != null) ? packageManager.getApplicationLabel(applicationInfo) : "???");
if (pkgName.equals(me._lastPkgName) == false) {
Runnable r = new Runnable() {
@Override
public void run() {
Bundle bundle = new Bundle();
if (me._lastPkgName != null) {
bundle.putString("PREVIOUS_APP_PKG", me._lastPkgName);
bundle.putString("PREVIOUS_APP_NAME", me._lastName);
bundle.putString("PREVIOUS_CATEGORY", RunningSoftwareProbe.fetchCategory(context, me._lastPkgName));
bundle.putLong("PREVIOUS_TIMESTAMP", me._lastStart);
}
me._lastPkgName = pkgName;
me._lastName = name;
bundle.putString("PROBE", me.name(context));
bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000);
bundle.putString("CURRENT_APP_PKG", pkgName);
bundle.putString("CURRENT_APP_NAME", name);
bundle.putString("CURRENT_CATEGORY", RunningSoftwareProbe.fetchCategory(context, pkgName));
me.transmitData(context, bundle);
me._lastStart = bundle.getLong("TIMESTAMP");
}
};
Thread t = new Thread(r);
t.start();
}
}
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
final String title = context.getString(R.string.title_app_usage_data_unavailable_alp);
if (prefs.getBoolean(ApplicationLaunchProbe.MUTE_ANDROID_FIVE_WARNING, ApplicationLaunchProbe.DEFAULT_ANDROID_FIVE_WARNING) == false) {
final String message = context.getString(R.string.message_app_usage_data_unavailable_alp);
sanity.addAlert(SanityCheck.WARNING, title, message, null);
}
else
sanity.clearAlert(title);
}
else
{
Runnable r = new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
UsageStatsManager usage = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
synchronized(usage)
{
final String title = context.getString(R.string.title_app_usage_data_required);
final String message = context.getString(R.string.message_app_usage_data_required);
long now = System.currentTimeMillis();
if (usage.queryEvents(now - (1 * 60 * 60 * 1000), now).hasNextEvent() == false)
{
Runnable action = new Runnable() {
@Override
public void run() {
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try
{
context.startActivity(intent);
sanity.clearAlert(title);
}
catch(Exception e)
{
LogManager.getInstance(context).logException(e);
Runnable r = new Runnable()
{
@Override
public void run() {
Toast.makeText(context, R.string.toast_missing_access_settings, Toast.LENGTH_LONG).show();
}
};
new Handler(Looper.getMainLooper()).post(r);
}
}
};
sanity.addAlert(SanityCheck.WARNING, title, message, action);
} else
{
sanity.clearAlert(title);
UsageEvents events = usage.queryEvents(me._lastCheck, now);
me._lastCheck = now;
UsageEvents.Event event = new UsageEvents.Event();
while (events.getNextEvent(event)) {
if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
Bundle bundle = new Bundle();
if (me._lastPkgName != null) {
bundle.putString("PREVIOUS_APP_PKG", me._lastPkgName);
bundle.putString("PREVIOUS_APP_NAME", me._lastName);
bundle.putString("PREVIOUS_CATEGORY", RunningSoftwareProbe.fetchCategory(context, me._lastPkgName));
bundle.putLong("PREVIOUS_TIMESTAMP", event.getTimeStamp() / 1000);
}
me._lastPkgName = new String(event.getPackageName());
if (me._appNames.containsKey(me._lastPkgName))
me._lastName = me._appNames.get(me._lastPkgName);
else {
PackageManager packages = context.getPackageManager();
try {
me._lastName = packages.getApplicationLabel(packages.getApplicationInfo(me._lastPkgName, PackageManager.GET_META_DATA)).toString();
} catch (NameNotFoundException e) {
me._lastName = me._lastPkgName;
}
me._appNames.put(me._lastPkgName, me._lastName);
}
bundle.putString("PROBE", me.name(context));
bundle.putLong("TIMESTAMP", event.getTimeStamp() / 1000);
bundle.putString("CURRENT_APP_PKG", me._lastPkgName);
bundle.putString("CURRENT_APP_NAME", me._lastName);
bundle.putString("CURRENT_CATEGORY", RunningSoftwareProbe.fetchCategory(context, me._lastPkgName));
me.transmitData(context, bundle);
}
event = new UsageEvents.Event();
}
}
}
}
}
};
Thread t = new Thread(r);
t.start();
alarm.setExact(AlarmManager.RTC, System.currentTimeMillis() + 60000, me._pollIntent);
}
}
}, new IntentFilter(ApplicationLaunchProbe.WAKE_ACTION));
}
if (disable && this._pollIntent != null)
{
alarm.cancel(this._pollIntent);
}
synchronized (this)
{
if (set && me._lastCheck == -1)
{
me._lastCheck = System.currentTimeMillis();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
alarm.setRepeating(AlarmManager.ELAPSED_REALTIME, 0, interval, this._pollIntent);
} else {
alarm.setExact(AlarmManager.RTC, System.currentTimeMillis(), this._pollIntent);
}
}
}
return isEnabled;
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
String app = bundle.getString("CURRENT_APP_NAME");
String category = bundle.getString("CURRENT_CATEGORY");
return String.format(context.getResources().getString(R.string.summary_app_launch_probe), app, category);
}
@Override
public Map<String, Object> configuration(Context context)
{
Map<String, Object> map = super.configuration(context);
SharedPreferences prefs = Probe.getPreferences(context);
long freq = Long.parseLong(prefs.getString(ApplicationLaunchProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
map.put(Probe.PROBE_FREQUENCY, freq);
boolean muteWarning = prefs.getBoolean(ApplicationLaunchProbe.MUTE_ANDROID_FIVE_WARNING, ApplicationLaunchProbe.DEFAULT_ANDROID_FIVE_WARNING);
map.put(Probe.PROBE_MUTE_WARNING, muteWarning);
return map;
}
@Override
public void updateFromMap(Context context, Map<String, Object> params)
{
super.updateFromMap(context, params);
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
if (params.containsKey(Probe.PROBE_FREQUENCY))
{
Object frequency = params.get(Probe.PROBE_FREQUENCY);
if (frequency instanceof Double)
{
frequency = ((Double) frequency).longValue();
}
if (frequency instanceof Long)
e.putString(ApplicationLaunchProbe.FREQUENCY, frequency.toString());
}
if (params.containsKey(Probe.PROBE_MUTE_WARNING))
{
Boolean muteWarning = (Boolean) params.get(Probe.PROBE_MUTE_WARNING);
e.putBoolean(ApplicationLaunchProbe.MUTE_ANDROID_FIVE_WARNING, muteWarning);
}
e.commit();
}
@Override
public String summary(Context context)
{
return context.getString(R.string.summary_application_launch_probe_desc);
}
@Override
@SuppressWarnings("deprecation")
public PreferenceScreen preferenceScreen(Context context, PreferenceManager manager)
{
PreferenceScreen screen = super.preferenceScreen(context, manager);
screen.setTitle(this.title(context));
screen.setSummary(R.string.summary_running_software_probe_desc);
CheckBoxPreference enabled = new CheckBoxPreference(context);
enabled.setTitle(R.string.title_enable_probe);
enabled.setKey(ApplicationLaunchProbe.ENABLED);
enabled.setDefaultValue(ApplicationLaunchProbe.DEFAULT_ENABLED);
screen.addPreference(enabled);
FlexibleListPreference duration = new FlexibleListPreference(context);
duration.setKey(ApplicationLaunchProbe.FREQUENCY);
duration.setEntryValues(R.array.probe_app_launch_frequency_values);
duration.setEntries(R.array.probe_app_launch_frequency_labels);
duration.setTitle(R.string.probe_frequency_label);
duration.setDefaultValue(ApplicationLaunchProbe.DEFAULT_FREQUENCY);
screen.addPreference(duration);
CheckBoxPreference muteWarning = new CheckBoxPreference(context);
muteWarning.setTitle(R.string.title_mute_android_five_warning);
muteWarning.setKey(ApplicationLaunchProbe.MUTE_ANDROID_FIVE_WARNING);
muteWarning.setDefaultValue(ApplicationLaunchProbe.DEFAULT_ANDROID_FIVE_WARNING);
screen.addPreference(muteWarning);
return screen;
}
@Override
public JSONObject fetchSettings(Context context)
{
JSONObject settings = super.fetchSettings(context);
try
{
JSONArray values = new JSONArray();
values.put(true);
values.put(false);
JSONObject muteWarning = new JSONObject();
muteWarning.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_BOOLEAN);
muteWarning.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_MUTE_WARNING, muteWarning);
JSONObject frequency = new JSONObject();
frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
values = new JSONArray();
String[] options = context.getResources().getStringArray(R.array.probe_app_launch_frequency_values);
for (String option : options)
{
values.put(Long.parseLong(option));
}
frequency.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_FREQUENCY, frequency);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
return settings;
}
public String assetPath(Context context)
{
return "app-launch-probe.html";
}
}